# Xiaozhi Device Capability Specification v1.0 > Status: **Freeze Analysis(钉死语义)** > Date: 2026-08-31 > Based on: Phase 1–2 真实代码 · Master Plan · 教育 / 益智 / 陪伴 / 助手四类 App 反向归纳 > 本文不是又一份 Master Plan,也不是某一 Phase 的 Coding Plan。 > 本文回答:**以后新增 App,固件原则上不再改什么。** **Phase 2 已完成(见 `phase-2-review.md`):** D1 完成事件、D6/D7 v1 跟读采集、D8 v1 文本归档;SD 按 App 唯一名建树。 **下一编码阶段是 `phase-3-plan.md`:** TaskInstance 落 manager-api,**无新 Device 能力、不改固件。** 覆盖矩阵的可视化见 Canvas:[能力覆盖矩阵](C:\Users\shi\.cursor\projects\d-xiaozhiAI\canvases\xiaozhi-device-capability-spec.canvas.tsx)。 --- ## 0. 冻结原则 ```text 新增 App → 只改 Server:App / Task / Step / Assessment / Resource → Device:读取配置或执行已有 Capability → 禁止:为单个 App 改 C++、改协议字段含义、强制 OTA ``` 允许 OTA 的唯一理由:补齐 **本 Spec 已声明、当前固件尚未具备** 的原子能力(例如统一 `play_audio` 完成事件、listen 完成事件)。不允许为「成语接龙」单独 OTA。 三层模型: ```text Application 教育 / 益智 / 陪伴 / 助手 ← 无限扩展,全在 Server + 资源 ↓ Task Runtime Task / Step / Flow / 评估 ← 尽快稳定,权威在 Server ↓ Device Capability 播 / 听 / VAD / 等待 / 指示 ← 现在冻结语义 ``` 再拆一层,避免把「麦克风」直接当 API: ```text Device Primitive I2S / 按键 GPIO / LED / SD / 网络 ↓ Interaction Capability play_audio / listen / vad / speak / wait / indicate ↓ Task Capability 跟读一轮、口算一题、接龙一回合 ← 禁止写进固件 ``` **判定规则:** 若完成条件依赖「答案是否正确 / 语义是否合格 / 该不该下一题」,这是 Server Task Capability,不是 Device Capability。 --- ## 1. Device 原子能力清单 ### 1.1 必须冻结(Device Interaction Capability) | ID | 能力 | 一句话 | |----|------|--------| | D1 | `play_audio` | 播放一项音频资源,可循环,必有完成/停止事件 | | D2 | `audio.pause` / `resume` / `stop` | 打断或挂起当前播放源(与 TTS 互斥) | | D3 | `speak` | 朗读文本;实现可以是云 TTS,设备只负责出声与打断 | | D4 | `barge_in` | 播放中检测到用户说话 → 停播 → Listening | | D5 | `play_system_sound` | 统一提示音,App 不得自带 ding 实现 | | D6 | `listen` | 上行语音流(现有 Opus),可按 Mode 不进 Chat | | D7 | `vad` | 上报用户开始说 / 停止说(硬件可观测) | | D8 | `record_clip` | v1 可用 listen 流 + Server 归档;v1.1 本地文件 + 上传 | | D9 | `button` | 短按 / 长按 / 双击 → 开始、停止、退出 Task | | D10 | `indicate` | 把逻辑态映射到 LED/屏,App 不写 GPIO | | D11 | `wait` | 可取消的本地延时 | | D12 | `volume` | 播放音量 | | D13 | `resource.fetch` | 把资源落到 SD/缓存(现有 DOWNLOAD 的语义升级) | | D14 | `task_control` | start / pause / resume / stop / restart **当前 Task 执行器** | ### 1.2 允许在 Device 执行、但参数来自配置的控制结构 | ID | 能力 | 范围 | |----|------|------| | D15 | `loop`(播放级) | `play_audio(loop=N)`,完成条件 = 播完 N 次。Phase 1 已有 `repeat` | | D16 | `loop`(步骤级) | **Server Task Runtime** 重复下发 Step;设备不解释「再做一题」 | ### 1.3 禁止进入 Device 的能力(Server 原子能力) | ID | 能力 | 原因 | |----|------|------| | S1 | ASR | 设备只有 PCM/Opus | | S2 | LLM | — | | S3 | Translation | Phase 1 已证明走 Server | | S4 | Assessment / Parser / 判题 | 口算、倒背、接龙对错都不在固件 | | S5 | Memory / Knowledge / RAG | — | | S6 | Task Generation / 动态出题 | — | | S7 | Speech feature → 临床诊断 | 特征可后抽,诊断永不上设备 | | S8 | `branch(if correct)` | 设备不知道 correct;若将来做精练模式,只执行「再播 / 下一题」命令。**默认教育 App 热路径不用 S8**:定时结束即下一步(见 phase-4-plan) | --- ## 2. 每个能力的输入 / 输出 下列为 **语义契约**。MCP 名、MQTT action、未来 `type=command` 都只是运输。改运输不算引进新能力。 ### D1 `play_audio` ```text in: resource: { kind: sd_path | http | asset_id, uri } volume?: 0-100 loop?: int >= 1 # 默认 1;在设备上循环 speed?: 0.5-2.0 float # 默认 1.0;超出夹紧。重采样实现,任意浮点,非离散档;非 1.0 会变调 power_off_after?: bool # 已有软关机挂钩,可保留为播放策略而非 App 逻辑 out (events): audio.started audio.completed # 自然结束(含 loop 全部完成) audio.stopped # 被 stop / 抢占 / 用户退出 audio.paused audio.resumed audio.failed # 缺文件、解码失败 ``` **当前代码:** Sening `self.mp3.play(name, speed, repeat, power_off_after)`;Phase 2 已上报 `audio.completed` / `audio.stopped`(运输:`[skill_event]...|duration_ms`)。无统一 WAV/HTTP play。 **冻结缺口(仍开放):** 资源 ID 而不是业务路径散落;非 Sening 板无此 Skill;`audio.paused/resumed/failed` 未作为独立事件。 ### D2 播放控制 ```text audio.pause | audio.resume | audio.stop ``` 当前:`self.mp3.stop`、TTS 时 `PauseSdCardMp3`。pause/resume 对 SD MP3 已有「TTS 暂停」语义,需提升为可被 Task 调用,而不仅是 Chat 副作用。 ### D3 `speak` ```text in: text, optional interruptible: bool out: speak.started, speak.completed, speak.interrupted ``` 当前:Server TTS → `type=tts` + Opus。设备没有独立「本地 TTS Skill」。**冻结:设备负责出声与 barge_in,不负责合成。** App/Task 只写 `speak("请回答")`。 ### D4 `barge_in` ```text policy (由 InteractionMode 配置,不是 App 名): CHAT / BILINGUAL 允许打断 TTS PLAY_AUDIO 默认不允许把喇叭当用户插话(复读) FOLLOW_READ/SHADOWING 播放范读时忽略拾音;范读结束后才 listen TRANSLATION 播译文时忽略回声 ``` 当前:Server 已按 mode 限制 AEC 打断。这是 **Mode 策略**,必须冻结进 Runtime,禁止每个 App 自己 if。 ### D5 `play_system_sound` ```text in: type = ding | beep | success | error | ... out: sound.completed ``` 当前:`AudioService::PlaySound` OGG。需编成稳定 type,而不是各板路径字符串。 ### D6 `listen` / D7 `vad` ```text listen.start(mode) listen.stop vad → user.speech.started | user.speech.stopped ``` 当前:上行 Opus + Server VAD;设备端也有 AFE/VAD 用于唤醒。 **冻结 v1:** 跟读/口算继续用现有听流 + Server VAD 事件即可,不强制设备再做一套文件录音。 **必须补的事件:** Server(或设备)在 Mode≠CHAT 时也要能区分「用户说完一句」,否则封闭式评估无法切 Step。 ### D8 `record_clip` 分两档,避免把冻结卡死在「设备写 WAV」: | 档 | 行为 | 覆盖 | |----|------|------| | v1 | listen 流 + Server `report` 归档(已有 chat audio BLOB) | 跟读、口算、倒背、接龙 | | v1.1 | `save_local` + `upload` + `record.completed(audio_ref)` | 弱网、会议纪要、律师询问长录音 | v1.1 **不阻塞** 用 5 个代表性 Task 冻结硬件。会议纪要类只增加「长录音上传」,不增加新传感器。 ### D9 `button` ```text short → toggle listen / 确认 long → 退出当前 Task 回 chat(可配) double → 预留 pause ``` 当前:BOOT 键绑 `ToggleChatState`。冻结时要允许「退出 Task」而不拆掉 Chat 键。 ### D10 `indicate` ```text in: IDLE | LISTENING | PROCESSING | PLAYING | ERROR | TASK_RUNNING | TASK_COMPLETE ``` 映射到 LED/屏是板级实现。App 只发逻辑态。当前 DeviceState 是硬件占用态,**不要**把 TASK_RUNNING 塞进 `Listening/Speaking` 迁移表。`indicate` 与 `DeviceState` 正交。 ### D11 `wait` ```text in: ms, cancellable out: wait.elapsed | wait.cancelled ``` 当前:**无**一等 Skill。复读循环把等待藏在播放器内部。冻结后「播放 → 等待 800ms → listen」不得靠 Server 连发两次再自己 sleep 充数(可先 Server 睡,但协议要留 `wait`,避免每个 App 各写各的)。 ### D12 `volume` / D13 `resource.fetch` 当前:MCP 音量、snlp `DOWNLOAD`。冻结为资源到达 SD 后 `play_audio` 只认本地 `resource_id`/`sd_path`。 ### D14 `task_control` ```text TASK_START / PAUSE / RESUME / STOP / RESTART ``` 当前:`START_APP_TASK`、`STOP_MEDIA`、优先级抢占。没有统一 PAUSE/RESUME **Task**(只有 MP3 被 TTS 暂停)。 PAUSE 语义:停播放与拾音,保留 pending 与 loop 剩余次数。STOP:清执行器,pending 回 `chat.conversation`。 ### D15 vs D16 Loop ```text 同一音频听 10 遍 → Device play_audio.loop=10 (已验证,保持) 10 道不同口算 → Server 循环 Step,每步 speak+listen 跟读 10 句不同音频 → Server 循环 Step(或 Task JSON 列表) ``` 禁止 Server 对同一文件连发 10 次 `play`(Phase 1 已否决)。 --- ## 3. Client Runtime API(逻辑,不是立刻改函数名) 设备执行器只暴露: ```text Capability.execute(name, args) -> accepted Capability.cancel(name) on Event(name, payload) ``` 对照现状: | 逻辑 API | 今日落点 | |----------|----------| | `play_audio` | `self.mp3.play` / `PLAY_MEDIA` | | `audio.stop` | `self.mp3.stop` / `STOP_MEDIA` | | `speak` | 被动收 `type=tts` | | `listen` | `SendStartListening` / 现有拾音 | | `wait` | 无 | | `indicate` | Display/LED 散落 | | `task_control` | `Board_StartAppTask` + preempt | | `resource.fetch` | MQTT DOWNLOAD | **冻结策略:** 新 App 只允许调用上表。板级 `self.mp3.*` 作为 `play_audio` 的 Sening 实现,逐步改名为逻辑名或保留别名。禁止再新增 `self.english.*`。 --- ## 4. Server Command(逻辑) Server → Device,最终至少这些 **命令语义**(运输可变): ```text TASK_START {app_id, task_id, params, source, instance_id?} TASK_PAUSE TASK_RESUME TASK_STOP TASK_RESTART PLAY_AUDIO {resource, loop, volume, speed} AUDIO_PAUSE | AUDIO_RESUME | AUDIO_STOP SPEAK {text} # 也可继续走现有 tts 帧,算同一能力 LISTEN_START | LISTEN_STOP WAIT {ms} INDICATE {state} SOUND {type} FETCH {url, dest} ``` 今日等价物:`type=mcp tools/call`、`type=tts`、MQTT `START_APP_TASK`。 **v1 运输冻结:可以继续 MCP + MQTT,不强制新 WS type。** **v1 语义冻结:上表集合不再为新 App 追加命令种类。** --- ## 5. Device Event(逻辑) Device → Server: ```text TASK_STARTED | TASK_COMPLETED | TASK_FAILED | TASK_CANCELLED STEP_STARTED | STEP_COMPLETED | STEP_FAILED # Step 权威仍在 Server;设备可只报 skill 完成 AUDIO_STARTED | AUDIO_COMPLETED | AUDIO_STOPPED | AUDIO_FAILED SPEAK_COMPLETED | SPEAK_INTERRUPTED LISTEN_STARTED USER_SPEECH_STARTED | USER_SPEECH_STOPPED RECORD_COMPLETED {audio_ref?} # v1.1 WAIT_ELAPSED BUTTON {kind: short|long|double} FETCH_PROGRESS | FETCH_COMPLETED | FETCH_FAILED ``` 今日等价物:MCP result、`[app_task]` / 规划中的 `[skill_event]`、MQTT `rpt/ack`。 跟读 v1 允许 USER_SPEECH_* 由 **Server VAD** 产生(设备未报也不算缺传感器)。 --- ## 6. 状态机(两套,禁止合并) ### 6.1 DeviceState(已有,冻结迁移表) ```text Idle ↔ Connecting ↔ Listening ↔ Speaking + Upgrading / WifiConfiguring / ... ``` 只回答:麦和喇叭现在归谁。Task 不得新增 `kDeviceStateEnglish`。 ### 6.2 InteractionMode(Server 已有字符串,固件可不枚举) ```text CHAT PLAY_AUDIO LISTEN # 只听,不自动 chat(跟读的听半拍;口算听答案) FOLLOW_READ # 先播后听;与今日 SHADOWING 对齐,建议最终统一命名 TRANSLATION BILINGUAL # 仍走 LLM,但是 Mode 策略(打断/提示词)不同 ``` QUIZ/GAME **不是**新 Mode。口算/倒背/接龙 = `speak + listen + Server 评估`,Mode 用 LISTEN 或 CHAT 策略开关 `skip_llm=true`。 ### 6.3 Task Executor(设备缓存) ```text IDLE → EXECUTING → WAITING_USER → COMPLETED | FAILED | CANCELLED PAUSE 可从 EXECUTING/WAITING_USER 进入,RESUME 返回 ``` 权威状态在 Server;设备缓存仅用于断线 UI 与离线复读。 --- ## 7. 中断 / 暂停 / 恢复 | 来源 | 行为 | |------|------| | 用户 barge_in(Mode 允许) | 停 TTS/音频 → listen | | 用户按钮长按 / 「退出」 | TASK_STOP → chat.conversation | | 家长 MQTT 即时 | 优先级 3 抢占,STOP 当前 Skill 再 START | | 计划任务 | 优先级 2,不可抢 MQTT 即时 | | 语音切 App | 优先级 1,不可抢 2/3 | | Chat TTS vs 课程 MP3 | 已有 PauseSdCardMp3,保持为音频互斥,不是 Task Pause | `TASK_PAUSE`:保留 loop 剩余、当前 step 索引(索引在 Server)。设备只暂停播放/拾音。 --- ## 8. 离线执行 分类冻结(Phase 1 已用事实证明): | 类型 | 没网 | 例子 | |------|------|------| | 可离线 Skill | 设备直接执行 | 复读 `play_audio` + loop | | 仅在线 Mode | 禁止 WakeWordInvoke 干等 | 翻译、跟读评估、接龙、口算 | | 入口口令 | MultiNet 只保留总入口 + 可离线 Skill | 系统词表按 `device.locale` 配置(Phase 6b);默认中文「切换模式」「复读机」。**禁止**把跟读/翻译/语言对口令塞进固件 | **禁止**把所有 App 口令塞进 MultiNet。 **禁止**把 `apps.json` 当作离线权威业务引擎;SD 上的 JSON 若存在,只是缓存的声明式配置 + 资源,对错仍不能离线判(除非以后做本地 parser——默认不做)。 --- ## 9. SD Card Resource Phase 1 决议仍然有效:**定义权威在 Server,不下发 App 商店。** 资源层与定义层分开。Phase 2 已落地的业务路径(经 `SDCard_Map_Business_Path` 接到 `MOUNT_POINT`): ```text /{app_id}/{task_id}/lessons/{lesson_id}/file.mp3 例: /english_learning/repeater/lessons/unit01/full.mp3 /english_learning/shadowing/lessons/unit01/01.mp3 ``` `app_id` 是 App 唯一名。用户订阅新 App 后由程序 mkdir(MCP `self.sd.ensure_app` / MQTT `ENSURE_APP_DIRS`),**不重烧固件**。这是运输暴露已有 `mkdir`,不是新传感器。 长期仍可增加缓存目录(Phase 6),不必再搬家上面这棵树: ```text /sdcard/ {app_id}/{task_id}/lessons/ # 音频(Phase 2 事实) cache/ recordings/pending/ # 仅 v1.1 apps/{app_id}/ # 可选:manifest 缓存(Phase 6);设备仍只执行 skills[] ``` 设备 **不知道**「这是英语 App」。它只知道: ```text resource_id → 文件 play_audio(resource_id) ``` Sening 现有 `/data/courses/...` 允许映射,不强迫一次搬家。 --- ## 10. 音频录制与上传 ```text v1 热路径:Mic → Opus 流 → Server ASR → 可选 WAV BLOB(现有 report) v1 跟读:同一条听流;禁止为跟读再开第二套编解码 v1.1 文件:SD pending → 联网 upload → audio_ref ``` 热路径禁止改:16k / 60ms Opus。`record_clip` 不得换一套 AAC 引擎来「优化会议」。 --- ## 11. VAD - 唤醒 / 命令词:设备 AFE(已有) - 句边界(跟读、口算):Server VAD 可用;设备可后补 `user.speech.stopped` - Mode 策略:范读/复读播放中 **不得** 把喇叭当 VAD 命中 这是所有封闭式语音评估的共同前提。 --- ## 12. TTS Task JSON 只出现 `speak(text)` 或 `play_audio(resource)`。 合成落点(云 / 服务端 / 未来本地)由 Runtime 选。固件禁止写死「跟读必须云 TTS」。 --- ## 13. Timer / Loop / Branch ```text Timer Device wait(ms) 播放与听之间的间隙 Loop Device:同一 play_audio N 次 Server:不同 Step 重复 Branch 仅 Server:if assessment.pass then next else retry Device 只收到 PLAY 或 SPEAK 或 STOP ``` **Branch 进入固件 = 口算判题进 C++。绝对禁止。** --- ## 14. App Manifest(声明,不是固件) 目标形态(Phase 6 落地,现在只冻结字段方向): ```text app_id name default_task tasks[]: task_id interaction_mode skills[] / steps[] # steps 在 Server 执行 resources[] # 要下载的音频 ``` 固件 **不解析业务规则**。最多:离线时读 cached task 的 `skills: [play_audio]` 以便复读。 --- ## 15. Task DSL(Server,设备不解释完整 DSL) 冻结方向: ```text Task Step: speak | play_audio | wait | listen | stop on listen.completed → Server ASR → (可选) Assessment → next Step ``` 设备看到的永远是 **单步 Capability**,不是「如果孩子答错就降难度」。 成语接龙 / 口算 / 倒背 的差异只在 Server 的 Parser 与 Prompt,不在固件。 --- ## 16. 哪些能力必须 Firmware 固化 一次(或少数几次)OTA 补齐后应长期稳定: ```text Codec / AEC / 唤醒 / MultiNet 宿主(词表可配,词义不写业务) WS + 色宁 MQTT 栈 MCP / Skill 宿主 SD 文件系统 + 下载 音频互斥(TTS vs play_audio) play_audio + loop + stop + 完成事件 listen 流 barge_in 与 Mode 策略表 button 事件 indicate 映射 wait task_control 抢占 软关机 / RTC 闹钟执行 pending(已有) ``` --- ## 17. 哪些能力绝对不能进入 Firmware ```text ASR / LLM / 翻译算法 / 评分 / 口算解析 / 成语词库 情绪与临床判断 if (app_id == "english") { ... } 业务树 为单个 App 增加 GPIO 协议 把 QUIZ 做成新 DeviceState 把 apps.json 编译进固件当唯一词表 ``` 固件里出现 `english_learning` 字符串仅允许作为 **Skill 查找表键**(Phase 1 已有),不允许在旁边写音频逻辑。长期应用 `task.skills[]` 缓存取代硬编码表。 --- ## 18. 用 5 个代表性 App/Task 做能力覆盖验证 不是因为这 5 个最赚钱,而是 **覆盖率最高**。产品化顺序仍可先复读/跟读;倒背/口算/接龙用于 **验证 Runtime 是否真冻结**,不必先做漂亮 UI。 | 代表 Task | 验证的 Device 能力 | 验证的 Server 能力 | 现状 | |-----------|-------------------|-------------------|------| | 英语复读机 | play_audio, loop, stop, 完成事件, resource, 离线 | Task 启动、优先级 | Phase 2:播放+loop+完成事件已有 | | 英语跟读 | play/speak, wait, listen, VAD, barge_in 策略, 音频归档 | ASR(旁路), 后评估定量, Session | Phase 2 采集已有;Phase 4:时钟步进 + txt 契约,比对不挡播放 | | 数字倒背 | speak, listen, VAD, wait | ASR, Sequence parser, **流内** score + 及时 speak | **未做**(口令槽已预留;Phase 4b inline) | | 10 以内口算 | speak, listen, wait, timer | 出题, parser, 默认流内反馈 | **未做** | | 成语接龙 | speak, listen, barge_in(CHAT 策略) | ASR, LLM/词库, **流内** 合法性与下一提示 | **未做** | 这 5 个做完(哪怕后三个只做 Server 编排 + 现有 listen/speak),Device 侧不应再出现新传感器类能力。 其后: ```text 故事复述 / AI 主持人 / 回忆录 / 心理陪伴 / 会议纪要 / 律师询问 ``` 只增加 Task Definition、Prompt、Memory、Assessment、(v1.1)长录音上传。 心理陪伴:**长期行为信号**(响应时延、完成率)来自 Event 时间戳,不是新硬件;**禁止**当临床诊断。 --- ## 19. 能力覆盖矩阵(归纳结果) 图例:`D` = 需要已冻结的 Device 能力;`S` = Server;空 = 不需要。 | App/Task | 播 | TTS | VAD | 听/录 | ASR | LLM | 评估 | wait/loop | 动态下一步 | |----------|----|-----|-----|-------|-----|-----|------|-----------|------------| | 纯读 / 复读 | D | D | | | | | | D | | | 英语跟读 | D | D | D | D | S | | S | D | S | | 中英翻译 | | D | D | D | S | S* | | | | | 中英对话 | | D | D | D | S | S | | | S | | 成语接龙 | | D | D | D | S | S | S | | S | | 口算 | | D | D | D | S | 可选 | S | D | S | | 数字倒背 | | D | D | D | S | | S | D | S | | 快问快答 | | D | D | D | S | S | S | D | S | | 故事复述 | D | D | D | D | S | S | S | | S | | AI 主持人 | | D | D | D | S | S | S | | S | | 回忆录 | | D | D | D | S | S | S | | S | | 会议纪要 | | | | D† | S | S | S | | | | 心理陪伴 | | D | D | D | S | S | S‡ | | S | \* 翻译可用专用模型,仍是 Server。 † 长录音偏 v1.1 `record_clip`。 ‡ 只做参与度/行为信号,不做诊断。 **硬件列只有:播、TTS 出声、VAD、听/录、wait/loop、按钮/指示。** 其余全是 Server。 --- ## 20. 与 Phase 1 代码的差距(冻结清单,不是现在就写完) 必须在「宣称 Device 已冻结」之前补齐的固件/协议语义: 1. `audio.completed` / `audio.stopped`(逻辑事件;运输可用 `[skill_event]` 或 MCP) ✅ Phase 2 2. Mode≠CHAT 时可靠的「一句说完」(VAD/listen.completed) ✅ Phase 2 v1(Server VAD + ASR + 听窗上限) 3. 跟读/评估路径的音频归档(现有 report 即可) ✅ Phase 2 文本 + report;Phase 3 可选 `audio_id` 挂 step 4. `wait` 是否一等能力:可先 Server 侧延时,但不要再为每个 App 复制 5. `task pause/resume` 语义(可后于 1–3) 6. `play_audio` 资源 ID 统一(可与 Phase 6 资源管理一起;音频路径继续用 `/{app}/{task}/lessons`) **不需要为冻结而做:** Assessment 算法、成语词库、口算出题器、type=task 全套、合并双 MQTT。 **Java 表是 Task Runtime(Phase 3),不是 Device 冻结条件。** --- ## 21. 对 Roadmap 的插入点 ```text Phase 1 薄 App Runtime ✅ ↓ 本 Spec Device Capability Freeze ✅ 文档 ↓ Phase 2 用跟读/复读验证 listen/VAD/事件 ✅ D1 / D6 / D7 v1 / D8 v1;见 phase-2-review.md ↓ Phase 3 TaskInstance 落库 ✅ 无新 Device 能力;见 phase-3-review.md ↓ Phase 4 时钟步进 + 后评估 ← 下一编码;见 phase-4-plan.md ↓ 倒背 / 口算 / 接龙 (Phase 4b:流内评估 + 及时 speak;反证固件冻结) ``` Phase 2 的工程价值是:**把采集 + 完成事件钉进 Runtime。** 已完成。 Phase 3 的工程价值是:**Instance 可查询。** 已完成。 跟读后评估是 Phase 4;倒背/接龙的及时反馈是 Phase 4b,不能混成后评估。施工单:`phase-4-plan.md`、`phase-4-test.md`、`roadmap.md`。 --- ## 22. 接受本 Spec 的标准 当且仅当团队同意下面四句,才允许进入后续编码(Phase 3 起同样适用): 1. 新 App 不得要求新的传感器或新的 WS 消息种类;只许用本文件命令/事件表。 2. 对错、出题、翻译、接龙词库只在 Server。 3. 离线只保证「可离线 Skill」(播放),不保证智能 Task。 4. SD 放资源;声明式 Task JSON 若缓存到 SD,也不等于固件懂英语。